package org.example.hibernateTest;

import org.example.entity.Pay;
import org.example.entity.Student;
import org.example.entity.Worker;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;

/**
 * @author zhaokuii11@163.com
 * @create 2021-11-21 22:27
 * @Description
 */
public class Demo1 {
    private SessionFactory sessionFactory;//会话工厂
    private Session session;//会话
    private Transaction transaction;//事务

    @Before //juint测试方法 之前执行
    public void init() {
        System.out.println("init");
        //1. 根据 Configuration创建 会话工厂
        sessionFactory = new Configuration().configure().buildSessionFactory();
        //2. 创建 会话
        session = sessionFactory.openSession();
        //3. 创建事务
        transaction = session.beginTransaction();

    }

    @After//juint测试方法 之后执行
    public void destroy() {
        //先开后关
        System.out.println("destroy");
        transaction.commit();
        session.close();
        sessionFactory.close();
    }

    @Test
    public void testComponent() {
        Pay pay = new Pay(1000, 2000, 3000);
        Worker worker = new Worker(null, "吉良吉影", pay);
        session.save(worker);
        System.out.println("worker = " + worker);
    }

    @Test
    public void testDynamicUpdate() {
        Student student = session.get(Student.class, 5);
        student.setAddress("22");
    }

    @Test
    public void testDoWord() {
        //通过 jdbc api来访问数据库的操作
        session.doWork(new Work() {
            @Override
            public void execute(Connection connection) throws SQLException {
                //调用存储过程。
//                String procedure = "{call testProcedure{}}";
//                CallableStatement cstmt = connection.prepareCall(procedure);
//                cstmt.execute();
                System.out.println("connection = " + connection);
            }
        });

    }

    /**
     * evict： 从 session缓存中把指定的持久化对象移除
     */
    @Test
    public void testEvict() {
        Student student1 = session.get(Student.class, 4);
        Student student2 = session.get(Student.class, 5);

        student1.setAddress("ee");
        student2.setAddress("ff");

        session.evict(student1);
    }

    /**
     * delete() 执行删除操作，只要 oid 和数据表中一条记录，就会准备执行 delete操作
     * ==》若 oid 在数据表中没有对应的记录，则抛出异常。
     * 可以通过设置 hibernate配置文件的属性 hibernate.use_identifier_rollback 为 true
     * ==》使删除对象后，把其 oid为null
     */
    @Test
    public void testDelete() {
//        Student student = new Student(1, "空条徐伦", new Date(), "end");
//        session.delete(student);
        Student student1 = session.get(Student.class, 4);
        session.delete(student1);
        System.out.println(student1);
    }

    /**
     * saveOrUpdate() 方法
     * 1. 如果 更新的id在数据库中不存在报错。【javax.persistence.OptimisticLockException】
     * 2. 如果 更新的id在数据库中，则进行修改。
     * 3. 如果 id为 null，则进行插入。
     */
    @Test
    public void testSaveOrUpdate() {
        Student student = new Student(11, "空条徐伦", new Date(), "end");
        session.saveOrUpdate(student);
    }

    /**
     * update() 方法
     * 1. 若更新一个持久化对象，不需要显示的调用 update方法，因为在调用
     * ==》Transaction 的 commit() 方法时，会先执行 session的 flush方法。
     * 2. 更新一个游离对象，需要显示的调用 session的 update 方法。
     * ==》可以把一个游离对象变为持久化对象
     * ==》【org.hibernate.LazyInitializationException 5.4.8.Final报错误 no Session】
     * <p>
     * 需要注意的点：
     * 1. 更新的的游离对象报错、更新数据表的记录会发送 update语句。
     * 2. 如何能让 update 方法不再盲目的发送 update语句呐？
     * ==》在 .hbm.xml文件的 class节点设置 select-before-update=true
     * ==》【默认为 false】但是通常不需要设置该属性。
     * 3. 若数据表中没有对应的记录，但是还调用了 update方法，会抛出异常。
     * 4. 当 update() 方法关联一个游离对象时，如果 session的缓存已经存在相同
     * ==》oid 的持久化对象没有任何异常，也不会进行修改。【5.4.8.Final】
     */
    @Test
    public void testUpdate() {
        Student student = session.load(Student.class, 1);
        transaction.commit();
        session.close();//关闭 session
        //student.setSid(22);
        //2. 创建 会话
        session = sessionFactory.openSession();//开一个新的 session
        //3. 创建事务
        transaction = session.beginTransaction();//开一个新的 事务
        Student student1 = session.get(Student.class, 1);
        //student1.setName("迪奥22"); //这个对象是一个游离对象 no Session
        //这就话可以不用写【更新游离对象 no Session】
        session.update(student);
    }

    /**
     * get(饿汉式) vs load(懒汉式)
     * 1. 执行 get方法：会立即加载对象，
     * ==》而执行 load方法，若不使用该对象，则不会立即执行查询操作，而返回一个代理对象
     * ==》get是 立即检索，load是 延迟检索
     * 2. 若数据表中没有对应的记录，且 session也没有关闭，同时需要使用对象时。
     * ==》get 返回 null。
     * ==》load 抛出异常。org.hibernate.ObjectNotFoundException
     * 3. load 方法可能会抛出异常 org.hibernate.LazyInitializationException
     * ==》load 返回的是一个代理对象，需要把这个代理对象填充的时候去查数据库，你把 session关了 连接段了，加载不了。
     * ==》【在需要初始化代理对象之前已经关闭 session了】
     * 4. 若数据表中没有对相应的记录，且 session也没有关闭，同时不需要使用对象时。
     * ==》get 返回 null
     * ==》load 若不适用该对象的任何属性，没问题；若需要初始化了，抛出异常。
     */
    @Test
    public void testLoad() {
        Student student = session.load(Student.class, 10);
        System.out.println(student.getClass().getName());//org.example.entity.Student$HibernateProxy$QvME3g5c
//        session.close();
//        System.out.println("student = " + student);
    }

    @Test
    public void testGet() {
        Student student = session.get(Student.class, 1);
        session.close();
        System.out.println("student = " + student);
    }

    /**
     * persist() 也会执行 insert 操作
     * 和 save() 有什么区别。
     * 1. 在 persist 方法之前，若对象已经有 id了，则不会执行 insert，而抛出异常
     * ==》javax.persistence.PersistenceException
     */
    @Test
    public void testPersist() {
        Student student = new Student(null, "吉良吉影6", new Date(), "end");
        student.setSid(66);
        session.persist(student);
    }

    /**
     * 这个主键是 hibernate根据算法生成的 id
     * save() 方法
     * 1. 是一个临时对象变为持久化对象
     * 2. 为对象分配 ID。
     * 3. 在 flush 缓存时 会发送一条 insert语句。
     * 4. 在 save 方法之前的 id时无效的。
     * 5. 持久化的对象的 id是不能被修改的(save之后修改)！！。
     * ==》javax.persistence.PersistenceException
     */
    @Test
    public void testSave() {
        Student student = new Student(null, "吉良吉影4", new Date(), "end");
        student.setSid(100);
        System.out.println("before = " + student);
        session.save(student);
        student.setSid(66);
        System.out.println("after = " + student);
    }

    /**
     * clear() 清理缓存
     * 没有调用 session的 clear方法的时候只会发送一条 select语句.
     * 调用 session的 clear方法的时候会发送两条 select语句。
     */
    @Test
    public void testClear() {
        Student student = session.get(Student.class, 1);
        session.close();
        Student student2 = session.get(Student.class, 1);
    }

    /**
     * refresh(): 会强制发送 select 语句，以使
     * Session缓存中对象的状态和数据表中对应的记录保持一致。
     * 当前 mysql(5.5.49)数据隔离级别：repeatable read，如果低版本的 hibernate需要设置隔离级别。
     */
    @Test
    public void testRefresh() {
        Student student = session.get(Student.class, 1);
        System.out.println(student);
        session.refresh(student);//刷新
        System.out.println(student);
    }
    /*
    Hibernate:
        select
            student0_.s_id as s_id1_0_0_,
            student0_.name as name2_0_0_,
            student0_.birthday as birthday3_0_0_,
            student0_.address as address4_0_0_
        from
            table_his.tab_student student0_
        where
            student0_.s_id=?
    Student{sid=1, name='哇哇', birthday=2021-11-29 16:02:22.0, address='北京'}
    Hibernate:
        select
            student0_.s_id as s_id1_0_0_,
            student0_.name as name2_0_0_,
            student0_.birthday as birthday3_0_0_,
            student0_.address as address4_0_0_
        from
            table_his.tab_student student0_
        where
            student0_.s_id=?
    Student{sid=1, name='吉良吉影', birthday=2021-11-29 16:02:22.0, address='北京'}
    */

    /**
     * flush：是数据库表中的记录和 session缓存中的对象的状态保持一致。为了保持一致，则可能会发送对应的 sql语句。
     * 1. 调用 Transaction 的 commit()方法中：先声明 session 的 flush方法，再提交事务
     * 2. flush() 方法可能会发送 sql语句，但是不会提交事务。
     * 3. 注意：
     * =a. 在未提交事务或显示的调用 session.flush()方法之前，也有可能进行 flush()操作。
     * =b. 执行 hql 或者 qbc查询，会先进行 flush()操作，以得到数据表的最新的记录。
     * =c. 若记录的 ID是由底层数据库使用自增的方式生成的，则在调用 save()方法时，就会立即发送 insert语句
     * 因为 save方法后，必须保证对象 id是存在的！！
     */
    @Test
    public void testSessionFlush() {
//        Student student = session.get(Student.class, 1);
//        student.setAddress("安徽");// session 检测到你修改了，在事务提交之前进行 flush发送update语句
        Student student = new Student(1, "哇哇", new Date(), "北京");
        session.save(student);
    }
    /*
    Hibernate:
        select
            student0_.s_id as s_id1_0_0_,
            student0_.name as name2_0_0_,
            student0_.birthday as birthday3_0_0_,
            student0_.address as address4_0_0_
        from
            table_his.tab_student student0_
        where
            student0_.s_id=?
    destroy
    Hibernate:
        update
            table_his.tab_student
        set
            name=?,
            birthday=?,
            address=?
        where
            s_id=?
    */


    @Test
    public void testSessionCache() {
        System.out.println("test");
        Student student = session.get(Student.class, 1);
        System.out.println("student :" + student);
        Student student2 = session.get(Student.class, 1);
        System.out.println("student2 :" + student2);
        System.out.println(student == student2);//true
    }
}
